import java.awt.Graphics;
import java.awt.Image;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Hashtable;
import java.util.Enumeration;
import javax.swing.JPanel;

public class Slate extends JPanel implements MouseListener,MouseMotionListener,KeyListener
  {
  private static final int fontSize=14;
  private static final Font font=new Font("monospaced",Font.PLAIN,fontSize);
  private FontMetrics metrics=getFontMetrics(font);
  private GGraph graph;
  private Counter counter;
  private Machine machine;
  public Slate(Machine machine)
    {
    this.machine=machine;
    setPreferredSize(new Dimension(1280,960));
    graph=new GGraph(machine);;
    counter=new Counter();
    addMouseListener(this);
    addMouseMotionListener(this);
    addKeyListener(this);
    setFocusable(true);
    }
    
  public GGraph getGraph()
    {
    return graph;
    }  
 
  public void setGraph(GGraph graph)
    {
    this.graph=graph;
    repaint();
    }
    
  //METODI GRAFICI
    
  public void update(Graphics g)
    {
    paint(g);
    }
 
  public void paint(Graphics G)
    {
    counter.clear();
    Image image=createImage(getWidth(),getHeight());
    Graphics g=image.getGraphics();
    Enumeration circles=graph.states();
    g.setColor(Color.lightGray);
    g.setFont(font);
    Transition t;
    while(circles.hasMoreElements())
      {
      Circle c=(Circle)circles.nextElement();
      drawCircle(c,g);
      Hashtable transitions=graph.exitingTransitions(c);
      Enumeration reads=transitions.keys();
      while(reads.hasMoreElements())
        {
        String read=(String)reads.nextElement();
        t=(Transition)transitions.get(read);
        if(t!=graph.getTransition()) drawTransition(t,g);
        }
      }
    t=graph.getTransition();
    if(t!=null)
      {
      g.setColor(Color.black);
      drawTransition(t,g);
      g.setColor(Color.lightGray);
      }
    G.drawImage(image,0,0,null);
    }
    
    
  private final int r=4;
  private final int ax=4;
  private final int ay=24;
  private void drawCircle(Circle circle,Graphics g)
    {
    int x=circle.getX();
    int y=circle.getY();
    int R=circle.getR();
    String label=circle.getLabel();
    if(circle==graph.getSelected())
      {
      g.setColor(Color.black);
      //g.fillOval(x-R,y-R,2*R,2*R);
      if(graph.isUnlocked()) label=label+"|";
      }
    g.drawOval(x-R,y-R,2*R,2*R);
    if(circle.isAccepting()) g.drawOval(x-R+4,y-R+4,2*R-8,2*R-8);
    if(circle==graph.getStart()) drawTip(g,new Vektor(circle.getX()-circle.getR(),circle.getY()),new Vektor(1,0));
    int width=metrics.stringWidth(label);
    g.drawString(label,x-width/2,y+fontSize/2);
    if(circle==graph.getSelected()) g.setColor(Color.lightGray);
    }
  private void drawTransition(Transition transition,Graphics g)
    {
    Circle start=(Circle)transition.getStart();
    Circle end=(Circle)transition.getEnd();
    Vektor s=new Vektor(start.getX(),start.getY());
    Vektor e=new Vektor(end.getX(),end.getY());
    Vektor v=(e.sub(s)).versor();
    int p=counter.get(transition);
    counter.inc(transition);
    if(start!=end)
      {
      Vektor sl=s.add(v.mul(start.getR()).rotate(-0.25));
      Vektor el=e.sub(v.mul(end.getR()).rotate(0.25));
      g.drawLine((int)sl.getX(),(int)sl.getY(),(int)el.getX(),(int)el.getY());
      drawTip(g,el,v);
      Vektor q=(sl.add(v.mul(16))).add(new Vektor(-16,6));
      Vektor sls=s.add(v.mul(start.getR()*1.75).rotate(-0.4));
      g.drawString(transition.getLabel(),(int)(sls.getX())-16,(int)(sls.getY()-16*(p+0.5))+16);
      }
    else
      {
      g.drawOval((int)s.getX()-ax,(int)s.getY()-start.getR()-2*ay,2*ax,2*ay);
      drawTip(g,e.sub(new Vektor(0,end.getR())),new Vektor(0,1));
      g.drawString(transition.getLabel(),(int)(s.getX())-16,(int)(s.getY()-start.getR()-2*ay-2*r)-16*p);
      }
    }

  private void drawTip(Graphics g,Vektor el,Vektor v)
    {
    Vektor t=el.sub(v.mul(8));      
    Vektor ta=t.add(v.orthogonal().mul(8));
    Vektor tb=t.sub(v.orthogonal().mul(8));
    g.drawLine((int)el.getX(),(int)el.getY(),(int)ta.getX(),(int)ta.getY());
    g.drawLine((int)el.getX(),(int)el.getY(),(int)tb.getX(),(int)tb.getY());
    }
  private int sign(double n)
    {
    if(n>=0) return 1;
    else return -1;
    }

    //**********//  
   //*LISTENER*//
  //**********//

  public void mousePressed(MouseEvent e)
    {
    requestFocusInWindow();
    if(e.getButton()==1)
      {
      machine.tryToLock();
      graph.addOrSelectCircle(e.getX(),e.getY());
      if(graph.isLocked())
        {
        graph.setTransitionEnd();
        graph.unselectTransition();
        String label=graph.getSelected().getLabel();
        if(e.getClickCount()==2||label.equals(""))
          {
          machine.unlock();
          }
        }
      }
    repaint();
    }
  public void mouseReleased(MouseEvent e)
    {
    }
  public void mouseClicked(MouseEvent e)
    {
    }
  public void mouseEntered(MouseEvent e)
    {
    }
  public void mouseExited(MouseEvent e)
    {
    }
  public void mouseMoved(MouseEvent e)
    {
    graph.setPointerPosition(e.getX(),e.getY());
    graph.setTransitionEnd(e.getX(),e.getY());
    repaint();
    }
  public void mouseDragged(MouseEvent e)
    {
    graph.setCirclePosition(e.getX(),e.getY());
    repaint();
    }
  public void keyPressed(KeyEvent e)
    {
    int key=e.getKeyCode();
    char c=e.getKeyChar();
    if(graph.isUnlocked())
      {
      Circle circle=graph.getSelected();
      String label=circle.getLabel();
      if(isAcceptable(key))
        {
        circle.setLabel(label+s(c));
        }
      else if(key==8)
        {
        if(label.length()>0)
          {
          circle.setLabel(label.substring(0,label.length()-1));
          }
        else
          {
          graph.removeCircle();
          graph.removeTransition();
          machine.tryToLock();
          }
        }
      else if(key==10)
        {
        machine.tryToLock();
        }
      }
     else
      {
      if(isAcceptable(key))
        {
        if(graph.getSelected()!=null)
          {
          graph.addOrSelectTransition(s(c));
          graph.unselectSelected();
          }
        else if(graph.getTransition()!=null)
          {
          graph.getTransition().setWrite(s(c));
          }
        }
      else if(key==8)
        {
        graph.removeCircle();
        graph.removeTransition();
        }
      else if(isMovement(key))
        {
        Transition t=graph.getTransition();
        if(t!=null)
          {
          t.setMove(key-38);
          }
        }
      }
    repaint();
    }
  private boolean isAcceptable(int key) 
    {
    return 48<=key&&key<=57||65<=key&&key<=90||44<=key&&key<=46||key==32||key==92||key==153||key==222||key==521;
    }
  private boolean isMovement(int key)
    {
    if(37<=key&&key<=40) return true;
    else return false;
    }
  private String s(char c)
    {
    char[] cs=new char[1];
    cs[0]=c;
    return new String(cs);
    }
  public void keyReleased(KeyEvent e)
    {
    }
  public void keyTyped(KeyEvent e)
    {
    }
  public void setStart()
    {
    graph.setStart();
    repaint();
    }
  public void changeAccepting()
    {
    graph.changeAccepting();
    repaint();
    }
  }
